package ferp.android.views;

import android.content.Context;
import android.graphics.*;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import ferp.android.GUI;
import ferp.android.R;
import ferp.android.managers.ProfileManager;
import ferp.android.widgets.buttons.BidButton;
import ferp.android.widgets.buttons.TextButton;
import ferp.android.widgets.buttons.rank.RankButton;
import ferp.android.widgets.buttons.suit.SuitButton;
import ferp.android.widgets.callouts.Callout;
import ferp.android.widgets.callouts.CalloutLeft;
import ferp.android.widgets.callouts.CalloutRight;
import ferp.core.calc.scorer.Passing;
import ferp.core.card.Card;
import ferp.core.game.*;
import ferp.core.mode.Moving;
import ferp.core.player.Hand;
import ferp.core.player.Player;
import ferp.core.player.Profile;

import java.util.*;

/**
 * User: igorgok
 * Date: 9/8/11 10:30 AM
 */
public class TableView extends ViewGroup implements CardView.Listener
{
  public static interface Listener
  {
    // offered button pressed
    void onOfferButtonPressed();
    // input event
    void onInputButtonClicked(View view, int id);
    // undo drop
    void onUndoDrop(Game g);
    // card moved to the table
    void onCardReleased(Card c);
    // tree is ready
    void onTreeReady(Player mover) throws Game.Error;
    // touch event when waiting for touch
    void onTouch();
    // commit animation ended
    void onCardCommit(Card card);
  }

  public final class Offer
  {
    public Input.Offer type;
    public String text;

    public Offer(Input.Offer type, String text)
    {
      this.type = type;
      this.text = text;
    }
  }

  public final class InputView
  {
    //public final FrameLayout layout = (FrameLayout)findViewById(R.id.input);
    public final View frame0 = findViewById(R.id.input_frame0);
    public final LinearLayout frame1 = (LinearLayout)findViewById(R.id.input_frame1);
    public final View frame2 = findViewById(R.id.input_frame2);
    public final BidButton bid = (BidButton)findViewById(R.id.bid);
    public final TextButton pass = (TextButton)findViewById(R.id.pass);
    public final TextButton misere = (TextButton)findViewById(R.id.misere);
    public final TextButton whistFull = (TextButton)findViewById(R.id.whist_full);
    public final TextButton whistHalf = (TextButton)findViewById(R.id.whist_half);
    public final TextButton whistPass = (TextButton)findViewById(R.id.whist_pass);
    public final TextButton stand2 = (TextButton)findViewById(R.id.stand2);
    public final TextButton layDown2 = (TextButton)findViewById(R.id.lay_down2);
    public final TextButton stand = (TextButton)findViewById(R.id.stand);
    public final TextButton layDown = (TextButton)findViewById(R.id.lay_down);
    public final TextButton drop = (TextButton)findViewById(R.id.drop);
    public final TextButton without3 = (TextButton)findViewById(R.id.without3);
    public final TextButton agree10 = (TextButton)findViewById(R.id.tricking10_agree);
    public final TextButton play10 = (TextButton)findViewById(R.id.tricking10_play);
    public final BidButton declare = (BidButton)findViewById(R.id.declare);
    public final RankButton declareRankUp = (RankButton)findViewById(R.id.declare_rank_up);
    public final RankButton declareRankDown = (RankButton)findViewById(R.id.declare_rank_down);
    public final SuitButton declareSuitUp = (SuitButton)findViewById(R.id.declare_suit_up);
    public final SuitButton declareSuitDown = (SuitButton)findViewById(R.id.declare_suit_down);
    public final TextButton misereUndo = (TextButton)findViewById(R.id.misere_undo);
    public final TextButton miserePlay = (TextButton)findViewById(R.id.misere_play);

    public final View[] elements = {
        pass, bid, misere, whistFull, whistHalf, whistPass, declare, declareRankUp, declareRankDown, declareSuitUp,
        declareSuitDown, stand, layDown, stand2, layDown2, drop, without3, agree10, play10, misereUndo, miserePlay
    };

    public InputView()
    {
      bid.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {listener.onInputButtonClicked(view, R.id.bid);}
      });

      pass.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {listener.onInputButtonClicked(view, R.id.pass);}
      });

      misere.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {listener.onInputButtonClicked(view, R.id.misere);}
      });

      without3.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {listener.onInputButtonClicked(view, R.id.without3);}
      });

      drop.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {listener.onInputButtonClicked(view, R.id.drop);}
      });

      whistFull.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {listener.onInputButtonClicked(view, R.id.whist_full);}
      });

      whistHalf.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {listener.onInputButtonClicked(view, R.id.whist_half);}
      });

      whistPass.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {listener.onInputButtonClicked(view, R.id.whist_pass);}
      });

      misereUndo.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {listener.onInputButtonClicked(view, R.id.misere_undo);}
      });

      miserePlay.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {listener.onInputButtonClicked(view, R.id.misere_play);}
      });

      agree10.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {listener.onInputButtonClicked(view, R.id.tricking10_agree);}
      });

      play10.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {listener.onInputButtonClicked(view, R.id.tricking10_play);}
      });

      stand.setOnClickListener(new OnClickListener()
      {
        @Override
        public void onClick(View view) {listener.onInputButtonClicked(view, R.id.stand);}
      });

      stand2.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {listener.onInputButtonClicked(view, R.id.stand2);}
      });

      layDown.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {listener.onInputButtonClicked(view, R.id.lay_down);}
      });

      layDown2.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {listener.onInputButtonClicked(view, R.id.lay_down2);}
      });

      declare.setOnClickListener(new OnClickListener()
      {
        @Override
        public void onClick(View view) {listener.onInputButtonClicked(view, R.id.declare);}
      });

      declareSuitUp.setOnClickListener(new OnClickListener()
      {
        @Override
        public void onClick(View view)
        {
          // check view's enable state first as onClick event might occur more than once if user presses on button too fast
          if (view.isEnabled())
          {
            Game g = ProfileManager.instance(getContext()).getCurrentProfile().game();

            g.options.bid.trump = Card.Suit.next(g.options.bid.trump);
            update4WayDeclareControl(g, g.options.bid);
          }
        }
      });

      declareSuitDown.setOnClickListener(new OnClickListener()
      {
        @Override
        public void onClick(View view)
        {
          // check view's enable state first as onClick event might occur more than once if user presses on button too fast
          if (view.isEnabled())
          {
            Game g = ProfileManager.instance(getContext()).getCurrentProfile().game();

            g.options.bid.trump = Card.Suit.previous(g.options.bid.trump);
            update4WayDeclareControl(g, g.options.bid);
          }
        }
      });

      declareRankUp.setOnClickListener(new OnClickListener()
      {
        @Override
        public void onClick(View view)
        {
          // check view's enable state first as onClick event might occur more than once if user presses on button too fast
          if (view.isEnabled())
          {
            Game g = ProfileManager.instance(getContext()).getCurrentProfile().game();

            ++g.options.bid.tricks;
            update4WayDeclareControl(g, g.options.bid);
          }
        }
      });

      declareRankDown.setOnClickListener(new OnClickListener()
      {
        @Override
        public void onClick(View view)
        {
          // check view's enable state first as onClick event might occur more than once if user presses on button too fast
          if (view.isEnabled())
          {
            Game g = ProfileManager.instance(getContext()).getCurrentProfile().game();

            --g.options.bid.tricks;
            update4WayDeclareControl(g, g.options.bid);
          }
        }
      });
    }
  }

  // controls card selection during dropping and moving
  private static final class CardSelector
  {
    private enum State {INACTIVE, DROPPING, MOVING}

    private void reset()
    {
      state = State.INACTIVE;

      selected.clear();
    }

    private void reset(Input.Options io)
    {
      if (io.isSet(Input.Options.DROP))
        state = State.DROPPING;
      else
        if (io.hand != 0)
          state = State.MOVING;
        else
          state = State.INACTIVE;

      selected.clear();
    }

    // check is current selector contains the card view
    private boolean contains(CardView cv) {return state != State.INACTIVE && selected.contains(cv);}

    // process user event
    private void process(TableView tv, CardView cv, Player p)
    {
      boolean contains = selected.contains(cv);

      switch (state)
      {
        case INACTIVE:
          // disable all candidates except the one that is moving to prevent moving few of them in the same time
          tv.enablePlayerCards(p.id(), false);
          cv.setEnabled(true);

        return;

        case DROPPING:
          // process only if human still has to pick either card to drop or already selected card
          if (selected.size() == Game.TALON_SIZE && !contains)
            return;
          break;
      }

      if (contains)
      {
        elevateIn(cv, p);
        selected.remove(cv);
      }
      else
      {
        elevateOut(cv, p);
        selected.add(cv);
      }

      complete(tv, cv, p);
    }

    private void elevateIn(CardView cv, Player p)
    {
      switch (p.id())
      {
        case 0:
          cv.layout(cv.getLeft(), cv.getTop() + ELEVATION);
          break;

        case 1:
          cv.layout(cv.getLeft() - ELEVATION, cv.getTop());
          break;

        case 2:
          cv.layout(cv.getLeft() + ELEVATION, cv.getTop());
          break;
      }
    }

    private void elevateOut(CardView cv, Player p)
    {
      switch (p.id())
      {
        case 0:
        cv.layout(cv.getLeft(), cv.getTop() - ELEVATION);
          break;

        case 1:
          cv.layout(cv.getLeft() + ELEVATION, cv.getTop());
          break;

        case 2:
          cv.layout(cv.getLeft() - ELEVATION, cv.getTop());
          break;
      }
    }

    private void complete(TableView tv, CardView cv, Player p)
    {
      switch (state)
      {
        case DROPPING:
          tv.inputView.drop.setEnabled(selected.size() == Game.TALON_SIZE);
          tv.inputView.without3.setEnabled(selected.size() == 0);
          break;

        case MOVING:
          int size = selected.size();

          // if selected size > 1 then elevate back previously selected card
          if (size > 1)
            for (Iterator<CardView> i = selected.iterator(); i.hasNext();)
            {
              CardView c = i.next();

              if (c != cv)
              {
                elevateIn(c, p);
                i.remove();
                --size;
              }
            }

          // in case there is selected card show target area in trick
          if (size == 1)
          {
            Profile profile = ProfileManager.instance(tv.getContext()).getCurrentProfile();
            Game g = profile.game();

            if (((Moving)g.mode()).mover(g, profile.settings, g.player.current) == g.human())
              tv.showTarget(g.player.current);
          }
          else
            tv.views.target.setVisibility(GONE);
          break;
      }
    }

    // get selected cards as array
    private Card[] cards()
    {
      Card[] cards = new Card[selected.size()];
      int i = 0;

      for (CardView cv : selected)
        cards[i++] = cv.card();

      return cards;
    }

    private CardView[] cardViews() {return selected.toArray(new CardView[selected.size()]);}

    // current selector's state
    private State state;
    // holds all selected cards
    private HashSet<CardView> selected = new HashSet<CardView>(Game.TALON_SIZE);
  }

  public TableView(Context context, AttributeSet as)
  {
    super(context, as);

    // create views
    views = new Views(context);

    setOnTouchListener(new OnTouchListener()
    {
      public boolean onTouch(View view, MotionEvent motionEvent)
      {
        if (waitForTouch)
        {
          listener.onTouch();
          waitForTouch = false;
        }

        return false;
      }
    });

    setWillNotDraw(false);
  }

  public void initialize(Listener listener)
  {
    this.listener  = listener;

    // set up the input layout
    inputView = new InputView();
  }

  public void reset(Game g, Settings settings)
  {
    // hide player bids
    hideBids(g);

    // reset last moving card
    mc.reset();

    // set player cards
    for (Player p : g.players())
    {
      // set new cards for the player and disable them
      showPlayerCards(g, settings, p.id());
      enablePlayerCards(p.id(), false);

      // hide bid views
      //views.bids[p.id()].setVisibility(INVISIBLE);
    }

    // show talon cards face down
    setTalon(g.talon());

    // gone trick cards
    hideTrick(g, false);

    // reset trick counters
    for (TrickCounterView tc : views.tcv)
     tc.reset();

    // reset card manager
    cs.reset();

    // gone misere desk
    views.mdv.reset();
    views.mdv.setVisibility(INVISIBLE);

    // gone fistHand, clock turn
    views.firstHand.hide();
    views.sandClock.hide();
    views.turn.hide();
    views.offer.setVisibility(INVISIBLE);

    views.replayIndicator.show(g.replay);

    for (CardView cv : views.drop)
      cv.setVisibility(INVISIBLE);
  }

  public void removeAnimations()
  {
    // does nothing in e-book edition
  }

  public void showOptions(Game g, Settings settings)
  {
    Input.Options options = g.options;

    // make sure the layout is above everything
    // !!! inputView.layout.bringToFront();
    // set card selector's state according to input options
    cs.reset(options);
    waitForTouch = options.isSet(Input.Options.WAIT_FOR_TOUCH);

    // show eligible buttons only
    if (cs.state == CardSelector.State.DROPPING)
    {
      inputView.drop.setEnabled(false);
      inputView.drop.setVisibility(View.VISIBLE);
    }

    if (options.isSet(Input.Options.PLAY))
    {
      inputView.bid.show(options.bid);
      inputView.bid.setVisibility(View.VISIBLE);
    }
    if (options.isSet(Input.Options.MISERE))
      inputView.misere.setVisibility(View.VISIBLE);
    if (options.isSet(Input.Options.PASS))
    {
      int factor = Passing.getTrickCost(g, settings);
      String pass = getResources().getString(R.string.input_pass);
      // do not show passing trick value in case of Rostov
      inputView.pass.set(g.bid != null || settings.isRostov() ? pass : GUI.getColoredText(pass + " ", "red", "×" + factor));
      inputView.pass.setVisibility(View.VISIBLE);
    }
    if (options.isSet(Input.Options.WHIST_HALF))
      inputView.whistHalf.setVisibility(View.VISIBLE);
    if (options.isSet(Input.Options.WHIST_FULL))
      inputView.whistFull.setVisibility(View.VISIBLE);
    if (options.isSet(Input.Options.WHIST_PASS))
      inputView.whistPass.setVisibility(View.VISIBLE);
    if (options.isSet(Input.Options.DECLARE))
    {
      views.undoDrop.setVisibility(VISIBLE);

      inputView.frame1.setVisibility(VISIBLE);
      inputView.declare.setVisibility(VISIBLE);
      inputView.declareRankUp.setVisibility(VISIBLE);
      inputView.declareRankDown.setVisibility(VISIBLE);
      inputView.declareSuitUp.setVisibility(VISIBLE);
      inputView.declareSuitDown.setVisibility(VISIBLE);
      // show declare button text and buttons up/down state
      update4WayDeclareControl(g, options.bid);
    }
    if (options.isSet(Input.Options.STAND))
    {
      if (Trick.size(g.trick.current) == 0)
      {
        inputView.stand.setVisibility(View.VISIBLE);
        inputView.layDown.setVisibility(View.VISIBLE);
      }
      else
      {
        inputView.stand2.setVisibility(View.VISIBLE);
        inputView.layDown2.setVisibility(View.VISIBLE);
      }
    }
    if (options.isSet(Input.Options.WITHOUT3))
    {
      inputView.without3.setEnabled(true);
      inputView.without3.setVisibility(View.VISIBLE);
    }
    if (options.hand != 0)
      enablePlayerCards(g.player.current, options.hand);
    if (options.isSet(Input.Options.TRICKING_10_CONFIRM))
    {
      inputView.agree10.setVisibility(View.VISIBLE);
      inputView.play10.setVisibility(View.VISIBLE);
    }
    if (options.isSet(Input.Options.MISERE_CONFIRM))
    {
      inputView.misereUndo.setVisibility(View.VISIBLE);
      inputView.miserePlay.setVisibility(View.VISIBLE);
    }
    if (options.isOfferSet())
      views.offer.setVisibility(View.VISIBLE);
  }

  public void showMisereDesk(Game g, Settings settings, int cards, boolean landscape)
  {
    // set declarer hand (with talon) to misere desk
    //views.mdv.set(profile.game().getDeclarerHand().current);
    views.mdv.set(cards, settings.rankOrder);

    layoutMisereDesk(g, landscape);

    views.mdv.setVisibility(VISIBLE);
  }

  public void markMisereDesk(Card c)
  {
    // mark card played by declarer
    views.mdv.mark(c);
  }

  public void reorderMisereDesk()
  {
    Profile p = ProfileManager.instance(getContext()).getCurrentProfile();

    views.mdv.switchOrder(p.settings.rankOrder);

    // this it to resolve problem of show/hide rank layouts
    layoutMisereDesk(p.game(), p.isLandscape());
  }

  public void hideTarget()
  {
    views.target.setVisibility(GONE);
  }

  private void showTarget(int id)
  {
    View target = views.target;
    Rect trick = areas.trick;
    CardView cv = views.tv.getTargetCardView(id);

    target.measure(MeasureSpec.EXACTLY | cv.getWidth(), MeasureSpec.EXACTLY | cv.getHeight());
    target.layout(trick.left + cv.getLeft(), trick.top + cv.getTop(), trick.left + cv.getRight(), trick.top + cv.getBottom());
    target.setVisibility(VISIBLE);
  }

  public void hideOptions()
  {
    cs.reset();

    for (View v : inputView.elements)
      v.setVisibility(View.GONE);

    views.undoDrop.setVisibility(GONE);
    views.offer.setVisibility(GONE);
  }

  public void update4WayDeclareControl(Game g, Bid bid)
  {
    inputView.declare.show(bid);
    inputView.declareRankUp.set(g, bid);
    inputView.declareRankDown.set(g, bid);
    inputView.declareSuitUp.show(g, bid);
    inputView.declareSuitDown.show(g, bid);
  }

  // show cards in player's hand
  public void showPlayerCards(Game g, Settings settings, Player p) {showPlayerCards(g, settings, p.id());}
  public void showPlayerCards(Game g, Settings settings, int pid) {showPlayerCards(settings, pid, g.getHand(pid).current, g.getHand(pid).suits, g.getHand(pid).open);}

  public Card[] getDrop() {return cs.cards();}

  // show given cards for specific player
  public void showPlayerCards(Settings settings, int player, int hash, ArrayList<Card.Suit> suits, boolean show)
  {
    Hand.getOrderedSuits(hash, settings.suitOrder, suits);
    List<CardView> cvs = views.hands.get(player);
    List<Card> cards = Hand.cards(hash, suits, settings.rankOrder, show);
    int i = 0;

    for (; i < cards.size(); i++)
    {
      cvs.get(i).setVisibility(VISIBLE);
      cvs.get(i).set(cards.get(i), show);
      // to show players cards uncomment the following string
      //cvs.get(i).set(cards.get(i), show || BuildConfig.DEBUG);
    }

    // hide all the rest
    for (; i < cvs.size(); ++i)
      cvs.get(i).setVisibility(GONE);

    // redraw player hand
    layoutPlayerCards(settings, player);
  }

  public void showInitialPosition(Game g, Settings settings)
  {
    // hide trick and turn views because set can be finished in the middle of a round
    views.tv.hide();
    views.turn.hide();

    // show talon cards
    openTalon();

    boolean drop = false;

    for (int i = 0; i < Game.PLAYERS; ++i)
    {
      int hand;

      // in case of passing or 'without 3' drop cards shouldn't be shown
      if (i == g.player.declarer && g.drop()[0] != 0 && g.drop()[1] != 0)
      {
        hand = Hand.remove(Hand.add(g.getDeclarerHand().initial, g.talon()), g.drop());
        drop = true;
      }
      else
        hand = g.getHand(i).initial;

      showBid(g.player(i), g.getBid(i), false);
      showPlayerCards(settings, i, hand, g.getHand(i).suits, true);
    }

    // show replay indicator and first hand
    showFirstHand(Game.next(g.player.dealer));
    views.replayIndicator.show(false);

    // show dropped cards
    if (drop)
    {
      layoutDropCards(g);
      showDropCards(g);
    }
  }

  public void enablePlayerCards(int id, boolean enable)
  {
    List<CardView> hv = views.hands.get(id);
    for (CardView cv : hv)
      cv.setEnabled(enable);
  }

  public void enablePlayerCards(int id, int hand)
  {
    List<CardView> hv = views.hands.get(id);
    for (CardView cv : hv)
      if (cv.getVisibility() == VISIBLE)
        cv.setEnabled(Hand.contains(hand, cv.card().hash));
  }  

  public void moveCardToTrick(int player, Card card, boolean android, long animationTime)
  {
    // "gone" the card from the player's hand
    CardView cv = views.find(player, card);

    // for android mover initialize the card to animate
    // in case of human mover it's already done
    if (android)
      mc.initialize(cv, null);

    // move the card
    mc.commit(player, animationTime);
  }

  public void hideTrick(Game g, Settings settings)
  {
    // if playing passing and not Rostov and round < 2 - hide played talon card as well
    hideTrick(g, g.bid == null && !settings.isRostov() && g.rounds < 2);
  }

  public void showTrick(int trick)
  {
    // show players cards by they turn
    for (int i = 0; i < Game.PLAYERS; ++i)
    {
      Card card = Trick.card(trick, i);

      if (card != null)
        views.tv.show(Trick.owner(trick, i), card);
    }
  }

  public void setTalon(int[] talon)
  {
    int i = 0;

    for (CardView cv : views.talon)
      cv.set(Card.card(talon[i++]), false);
  }

  public void openTalon(int i)
  {
    // face up talon card
    CardView talon = views.talon.get(i);

    talon.show(true);
    talon.setVisibility(VISIBLE);
    talon.bringToFront();
  }

  public void openTalon()
  {
    openTalon(0);
    openTalon(1);
  }

  public void closeTalon(int i)
  {
    // face down talon card
    CardView talon = views.talon.get(i);

    talon.show(false);
    talon.setVisibility(VISIBLE);
  }

  public void hideTalon(int i)
  {
    views.talon.get(i).setVisibility(INVISIBLE);
  }

  public void hideTalon()
  {
    hideTalon(0);
    hideTalon(1);
  }

  public void showBid(Player p, Game.BidData bid, boolean animation)
  {
    // update the "model"
    bid.visible = true;

    // animate only for androids and not resumed game
    views.callouts[p.id()].show(bid.current, !Player.isHuman(p) && animation);
  }

  public void hideBids(Game g)
  {
    for (Player p : g.players())
    {
      // update the "model"
      g.getBid(p.id()).visible = false;
      // hide the callout
      views.callouts[p.id()].hide();
    }
  }
  
  public void updateTrickCount(Game g, Player p)
  {
    views.tcv[p.id()].update(g.getTricks(p.id()));
  }

  public void setTrickCount(int player, int tricks)
  {
    views.tcv[player].set(tricks);
  }

  public void showFirstHand(int first)
  {
    Element e = views.firstHand;

    e.view.setBackgroundResource(first == 1 ? R.drawable.first_hand_left : R.drawable.first_hand_right);
    e.show(areas.firstHand[first]);
  }

  public void hideFirstHand()
  {
    views.firstHand.hide();
  }

  public void showClock(int id)
  {
    hideFirstHand();
    views.sandClock.show(areas.clock[id]);
  }

  public void hideClock()
  {
    views.sandClock.hide();
  }

  public void showTurn(int id)
  {
    hideFirstHand();
    views.turn.show(areas.turn[id]);
  }

  public void hideTurn()
  {
    views.turn.hide();
  }

  public void onMovingStarted(CardView cv, Point touch)
  {
    Game g = ProfileManager.instance(getContext()).getCurrentProfile().game();

    cs.process(this, cv, g.current());
  }

  public void onMoving(CardView cv, int x, int y)
  {
    // does nothing in e-book version
  }

  public void onMovingEnded(CardView cv)
  {
    // does nothing in e-book version
  }

  public void reorderCards(Game g, Settings settings)
  {
    // refresh player cards (card order might have changed)
    for (Player p : g.players())
      showPlayerCards(g, settings, p);
  }

  @Override
  protected void onDraw(Canvas canvas)
  {
    super.onDraw(canvas);

    areas.draw(canvas);
  }

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b)
  {
    Profile profile = ProfileManager.instance(getContext()).getCurrentProfile();
    Game g = profile.game();

    if (g != null)
    {
      int width = r - l;
      int height = b - t;

      if (changed)
      {
        boolean landscape = profile.isLandscape();

        // initialize card size
        CardView.WIDTH = Math.round((landscape ? 0.107421875f : 0.15f) * width);
        CardView.HEIGHT = Math.round(CardView.WIDTH / (landscape ? 0.714f : 0.64f));

        PADDING = CardView.WIDTH / 10;
        ELEVATION = PADDING * 2;

        areas.setup(width, height, landscape);

        layoutTalonCards();
        layoutTrick();
        layoutAICards(profile.settings, 1);
        layoutAICards(profile.settings, 2);
        layoutHumanCards(profile.settings);

        views.firstHand.layout();
        views.sandClock.layout();
        views.turn.layout();

        layoutMisereDesk(g, landscape);
        layoutDropCards(g);
      }

      // layout bid & trick counters
      for (Player p : g.players())
      {
        layoutBid(p.id());
        layoutTrickCounter(p.id());
      }

      // layout undo drop button
      layoutUndoDrop();
      // layout input controls
      layoutInputControls(profile.settings);
      // layout offer button
      layoutOffer();
      // layout replay indicator
      views.replayIndicator.layout(areas.replayIndicator);
      views.replayIndicator.show(g.replay);
    }
  }

  private void layoutPlayerCards(Settings settings, int id)
  {
    if (Player.isHuman(id))
      layoutHumanCards(settings);
    else
      layoutAICards(settings, id);
  }

  private void hideTrick(Game g, boolean talon)
  {
/*
    for (CardView cv : views.trick)
      cv.setVisibility(INVISIBLE);
*/

    views.tv.hide();

    if (talon)
      views.talon.get(g.rounds).setVisibility(View.INVISIBLE);
  }

  private final class Views
  {

    final List<List<CardView>> hands = new ArrayList<List<CardView>>();
    final List<CardView> talon = new ArrayList<CardView>(Game.TALON_SIZE);
    final List<CardView> drop = new ArrayList<CardView>(Game.TALON_SIZE);
//    final List<CardView> trick = new ArrayList<CardView>(TRICK_SIZE);
    final TrickView tv;
    final MisereDeskView mdv;
    final Callout[] callouts = new Callout[Game.PLAYERS];
    final TrickCounterView[] tcv = new TrickCounterView[Game.PLAYERS];
    final Element firstHand;
    final Element sandClock;
    final Element turn;
    final Element replayIndicator;
    final Button undoDrop;
    final Button offer;
    final ImageView target;

    Views(Context context)
    {
      // create first hand text view
      firstHand = new Element(context, TableView.this) {
        @Override
        protected View setup(Context context)
        {
          TextView v = new TextView(context);

          v.setGravity(Gravity.CENTER);
          v.setTypeface(GUI.Font.BOLD);
          v.setTextColor(GUI.Color.NORMAL);
          v.setText("1");

          return v;
        }

        @Override
        protected void onBeforeLayout(Rect area)
        {
          ((TextView)view).setTextSize(TypedValue.COMPLEX_UNIT_PX, Math.round(area.height() / 2.5f));
        }
      };

      // create sand clock image view
      sandClock = new Element(context, TableView.this) {
        @Override
        protected View setup(Context context)
        {
          ImageView v = new ImageView(context);

          v.setBackgroundResource(R.drawable.sand_clock);

          return v;
        }

        @Override
        protected void onShow()
        {
          // does nothing in e-book version
        }

        @Override
        protected void onHide()
        {
          // does nothing in e-book version
        }
      };

      turn = new Element(context, TableView.this) {
        @Override
        protected View setup(Context context)
        {
          ImageView iv = new ImageView(context);

          iv.setBackgroundResource(R.drawable.turn);

          return iv;
        }
      };

      replayIndicator = new Element(context, TableView.this)
      {
        @Override
        protected View setup(Context context)
        {
          TextView v = new TextView(context);

          v.setGravity(Gravity.CENTER);
          v.setTypeface(GUI.Font.NORMAL);
          v.setTextColor(GUI.Color.WATERMARK_TABLE);
          v.setText(R.string.dialog_pool_replay_noun);
          //v.setShadowLayer(1, 1, 1, 0xFFFFFFFF);

          return v;
        }

        @Override
        protected void onBeforeLayout(Rect area)
        {
          GUI.FitText.byFactors((TextView) view, 0.8f, 0.5f);
        }
      };

      // create talon card views
      create(context, talon, Game.TALON_SIZE, true);
      // create drop card views
      create(context, drop, Game.TALON_SIZE, false);

      // trick view
      tv = new TrickView(context, TableView.this);
      addView(tv);

      // target view for moving card to trick
      target = new ImageView(getContext());
      target.setBackgroundColor(0xDDDDDDDD);
      target.setVisibility(GONE);
      target.setImageResource(R.drawable.target);
      target.setOnTouchListener(new OnTouchListener()
      {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent)
        {
          // prevent processing this event more than once for different motion events
          if (motionEvent.getAction() == MotionEvent.ACTION_DOWN)
          {
            Profile p = ProfileManager.instance(getContext()).getCurrentProfile();
            Game g = p.game();

            // hide the target view as move has been done
            view.setVisibility(GONE);

            // show the card in the trick
            CardView cv = cs.cardViews()[0];
            views.tv.show(g.player.current, cv.card());

            // hide the card in player's hand
            cv.setVisibility(GONE);

            mc.initialize(cv, null);
            listener.onCardReleased(cv.card());
          }

          return true;
        }
      });

      addView(target);

      // create callouts and trick counters
      for (int i = 0; i < Game.PLAYERS; ++i)
      {
        // create bid views
        callouts[i] = (i == 2 ? new CalloutRight(context) : new CalloutLeft(context));
        callouts[i].setVisibility(INVISIBLE);

        addView(callouts[i]);

        // create trick counter views
        tcv[i] = new TrickCounterView(context, TableView.this);
      }

      // create offer button
      offer = new Button(context);
      offer.setBackgroundResource(R.drawable.selector_button_offer);
      offer.setGravity(Gravity.CENTER);
      offer.setVisibility(INVISIBLE);
      addView(offer);

      offer.setOnClickListener(new OnClickListener()
      {
        public void onClick(View view)
        {
          listener.onOfferButtonPressed();
        }
      });

      // create undoDrop button
      undoDrop = new Button(getContext());
      undoDrop.setBackgroundResource(R.drawable.selector_button_undo_drop);
      undoDrop.setTypeface(GUI.Font.BOLD);
      undoDrop.setTextColor(GUI.Color.NORMAL);
      undoDrop.setVisibility(GONE);
      addView(undoDrop);

      undoDrop.setOnClickListener(new OnClickListener()
      {
        @Override
        public void onClick(View view)
        {
          if (view.isShown())
            listener.onUndoDrop(ProfileManager.instance(getContext()).getCurrentProfile().game());
        }
      });

      // create card views for players (hands)
      for (int i = 0; i < Game.PLAYERS; ++i)
      {
        List<CardView> hand = new ArrayList<CardView>();

        // create card views for player "i"
        create(context, hand, Game.HAND_SIZE + Game.TALON_SIZE, false);
        hands.add(hand);
      }

      Collections.reverse(hands);

      // create misere desk view (after the cards are created to ensure it's on top of them)
      mdv = new MisereDeskView(context);
      mdv.setVisibility(INVISIBLE);
      addView(mdv);
    }

    // create a number of CardView objects designated by "count", adding them to the supplied list and to "all"
    private void create(Context context, List<CardView> list, int count, boolean talon)
    {
      for (int i = 0; i < count; ++i)
      {
        CardView cv = new CardView(context, TableView.this);

        // populate the supplied list
        list.add(cv);
        // disable
        cv.setEnabled(false);

        // add the card view to the list of this view groups' children
        if (!talon)
          addView(cv);
      }

      if (talon)
      {
        ListIterator<CardView> iter = list.listIterator(list.size());
        while (iter.hasPrevious())
          addView(iter.previous());
      }
    }

    // find a card view backed by a provided card
    private CardView find(int id, Card c)
    {
      List<CardView> hand = hands.get(id);

      for (CardView cv : hand)
        if (cv.card() == c)
          return cv;

      return null;
    }
  }

  private final class MovingCard
  {
    void reset()
    {
      view = null;
    }

    void initialize(CardView cv, Point touch)
    {
      view = cv;
      // store view's origin
      origin.set(cv.getLeft(), cv.getTop());

      // calculate an offset from the touch point to left upper corner (used for adjustments during onMoving)
      if (touch != null)
        offset.set(touch.x - origin.x, touch.y - origin.y);
      else
        offset.set(0, 0);

      //Log.log.debug(Log.TAG, "initialized card " + cv.card() + ", enabled: " + cv.isEnabled()); // + origin: " + origin + ", touch: " + touch + ", offset: " + offset);
    }

    void move(int x, int y)
    {
      // calculate adjustment
      int left = x - offset.x;
      int top = y - offset.y;

      // layout the card within the current view borders
      if (left >= 0 && top >= 0 && left + view.getWidth() <= TableView.this.getWidth() && top + view.getHeight() <= TableView.this.getHeight())
      {
        view.layout(left, top);

        //Log.log.debug(Log.TAG, "move: " + new Point(left, top));
      }
    }

    void commit(int player, long animationTime)
    {
      Settings settings = ProfileManager.instance(getContext()).getCurrentProfile().settings;

      // show the card in the trick
      views.tv.show(player, view.card());
      // hide the card
      view.setVisibility(GONE);
      // redraw the hand
      layoutPlayerCards(settings, player);

      // notify the listener about commit animation end
      listener.onCardCommit(view.card());
    }

    void rollback(long animationTime)
    {
      Game g = ProfileManager.instance(getContext()).getCurrentProfile().game();

      view.layout(origin.x, origin.y);
      // enable the card
      view.setEnabled(true);

      // enable back all candidates as the player didn't make a move
      if (g.options.hand != 0)
        enablePlayerCards(g.player.current, g.options.hand);
    }

    // moving card view
    CardView view;
    // moving card view origin point
    final Point origin = new Point();
    // touch point shift from upper left corner of the card view
    final Point offset = new Point();
  }
  
  private static final class Areas
  {
    // callout areas
    final Rect[] callout = rect3();
    // first hand areas
    final Rect[] firstHand = rect3();
    // trick counter areas
    final Rect[] trickCounter = rect3();
    // glove areas
    final Rect[] turn = rect3();
    // offer area
    final Rect offer = new Rect();
    // clock areas
    final Rect[] clock = rect3();
    // talon area
    final Rect talon = new Rect();
    // trick area and sink
    final Rect trick = new Rect();
    final Rect sink = new Rect();
    // hand areas
    final Rect[] hand = rect3();
    // undo drop area
    final Rect undoDrop = new Rect();
    // replay indicator area
    final Rect replayIndicator = new Rect();
    // initial position drop area
    final Rect[] drop = rect3();
    // blind/open area in portrait
    final Rect bo = new Rect();
    // blind button's layout area in landscape
    final Rect blind = new Rect();
    // open button's layout area in landscape
    final Rect open = new Rect();

    // paint
    final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    
    void setup(int width, int height, boolean landscape)
    {
      // layout area paint
      //paint.setColor(0xFF006600);
      paint.setColor(Color.RED);
      paint.setStyle(Paint.Style.STROKE);

      // trick counter size
      int tcs = Math.round(landscape ? width / 15.f : height / 11.5f);

      // layout Android callout areas
      int cow = CardView.WIDTH;
      int coh = tcs;

      if (landscape)
      {
        set(callout[1], PADDING + CardView.WIDTH + PADDING, PADDING, cow, coh);
        set(callout[2], width - PADDING - CardView.WIDTH - PADDING - cow, PADDING, cow, coh);
      }
      else
      {
        set(callout[1], PADDING, PADDING, cow, coh);
        set(callout[2], width - PADDING - CardView.WIDTH, PADDING, cow, coh);
      }

      // layout hand areas
      int aht;                                                          // android hand top
      int ahh;                                                          // android hand height
      int hhx;                                                          // human hand x
      int hht;                                                          // human hand top
      int hhw;                                                          // human hand width

      if (landscape)
      {
        aht = PADDING;
        ahh = height - PADDING - CardView.HEIGHT / 2;
        hhx = CardView.WIDTH + 2 * PADDING;
        hht = height - CardView.HEIGHT - PADDING;
        hhw = width - 2 * CardView.WIDTH - PADDING * 4;
      }
      else
      {
        aht = callout[1].height() + PADDING * 2;
        ahh = height - aht - 2 * PADDING - CardView.HEIGHT;
        hhx = PADDING;
        hht = aht + ahh + PADDING;
        hhw = width - PADDING * 2;
      }

      set(hand[1], PADDING, aht, CardView.WIDTH, ahh);
      set(hand[2], width - PADDING - CardView.WIDTH, hand[1].top, CardView.WIDTH, ahh);
      set(hand[0], hhx, hht, hhw, CardView.HEIGHT);

      // layout trick counter areas
      set(trickCounter[0], (width - tcs) / 2, height - 2 * PADDING - CardView.HEIGHT - tcs, tcs, tcs);
      if (landscape)
      {
        set(trickCounter[1], hand[1].right + PADDING, callout[1].bottom + PADDING, tcs, tcs);
        set(trickCounter[2], hand[2].left - PADDING - tcs, callout[2].bottom + PADDING, tcs, tcs);
      }
      else
      {
        set(trickCounter[1], hand[1].right + PADDING, hand[1].top, tcs, tcs);
        set(trickCounter[2], hand[2].left - PADDING - tcs, hand[2].top, tcs, tcs);
      }

      // layout human callout area (same height as the trick counter)
      set(callout[0], GUI.center(cow, hand[1].right, trickCounter[0].left), trickCounter[0].top, cow, tcs);

      // layout first hand areas
      int fhw = Math.min(width, height) / 10;
      int fhh = tcs;
      set(firstHand[0], trickCounter[0].right + (hand[2].left - trickCounter[0].right - fhw) / 2, trickCounter[0].top, fhw, fhh);
      if (landscape)
      {
        set(firstHand[1], trickCounter[1].left + (tcs - fhw) / 2, trickCounter[1].bottom + PADDING, fhw, fhh);
        set(firstHand[2], trickCounter[2].left + (tcs - fhw) / 2, trickCounter[2].bottom + PADDING, fhw, fhh);
      }
      else
      {
        set(firstHand[1], trickCounter[1].left + (tcs - fhw) / 2, callout[1].top, fhw, fhh);
        set(firstHand[2], trickCounter[2].left + (tcs - fhw) / 2, firstHand[1].top, fhw, fhh);
      }

      // layout turn areas
      set(turn[0], GUI.center(tcs, trickCounter[0].right, hand[2].left), trickCounter[0].top, tcs, tcs);
      set(turn[1], trickCounter[1].left, trickCounter[1].bottom + PADDING, tcs, tcs);
      set(turn[2], trickCounter[2].left, turn[1].top, tcs, tcs);

      // layout offer area
      set(offer, GUI.center(tcs, trickCounter[0].right, hand[2].left), turn[0].top - tcs - PADDING, tcs, tcs);

      // layout clock areas
      int cw = Math.round(Math.min(width, height) / 11.5f);
      int ch = tcs;
      set(clock[0], trickCounter[0].right + (hand[2].left - trickCounter[0].right - cw) / 2, trickCounter[0].top, cw, ch);
      set(clock[1], trickCounter[1].left + (tcs - cw) / 2, trickCounter[1].bottom + PADDING, cw, ch);
      set(clock[2], trickCounter[2].left + (tcs - cw) / 2, clock[1].top, cw, ch);

      // layout talon area
      int tw = Math.round(Math.min(trickCounter[2].left - trickCounter[1].right - 2 * PADDING, 1.5f * CardView.WIDTH));
      set(talon, (width - tw) / 2, PADDING, tw, CardView.HEIGHT);

      // layout sink area
      if (landscape)
        sink.set(trickCounter[1].centerX(), trickCounter[1].centerY(), trickCounter[2].centerX(), trickCounter[0].centerY());
      else
        sink.set(trickCounter[1].centerX(), Math.max(trickCounter[1].bottom, talon.bottom) + PADDING, trickCounter[2].centerX(), trickCounter[0].top - PADDING);

      // layout trick area - make it 10% wider than talon
      int th = Math.round(1.3f * CardView.HEIGHT);
      tw = Math.round(tw * 1.1f);
      set(trick, GUI.center(tw, talon.left, talon.right), GUI.center(th, sink.top, sink.bottom), tw, th);

      // undo drop area
      height = width = trickCounter[0].width();
      set(undoDrop, GUI.center(width, hand[1].right, trickCounter[0].left), trickCounter[0].top, width, height);

      width = hand[2].left - hand[1].right;
      height = width / 3;
      set(replayIndicator, hand[1].right, GUI.center(height, trickCounter[1].bottom, offer.top), width, height);

      // initial position's drop area
      set(drop[0], talon.left, trickCounter[0].top - CardView.HEIGHT - PADDING, tw, CardView.HEIGHT);
      set(drop[1], hand[1].right + PADDING, GUI.center(CardView.HEIGHT, hand[1].top, hand[1].bottom), tw, CardView.HEIGHT);
      set(drop[2], hand[2].left - drop[0].width() - PADDING, drop[1].top, tw, CardView.HEIGHT);

      // standing/lay down area
      if (landscape)
      {
        int w = trick.width();
        int h = trick.height();
        int t = trick.top;

        set(blind, GUI.center(w, trickCounter[1].right, trick.left), t, w, h);
        set(open, GUI.center(w, trick.right, trickCounter[2].left), t, w, h);
      }
      else
      {
        int w = trickCounter[2].right - trickCounter[1].left;
        int h = trickCounter[0].top - trick.top - CardView.HEIGHT;

        set(bo, trickCounter[1].left, trick.top + CardView.HEIGHT, w, h);
      }
    }
    
    void set(Rect r, int x, int y, int width, int height)
    {
      r.set(x, y, x + width, y + height);
    }

    Rect[] rect3()
    {
      Rect[] ra = new Rect[3];

      ra[0] = new Rect();
      ra[1] = new Rect();
      ra[2] = new Rect();

      return ra;
    }
    
    void draw(Canvas c)
    {
/*
      c.drawRect(talon, paint);
      c.drawRect(trick, paint);
      c.drawRect(sink, paint);
      c.drawRect(hand[0], paint);
      c.drawRect(hand[1], paint);
      c.drawRect(hand[2], paint);

      for (Rect r : trickCounter)
        c.drawRect(r, paint);

      for (Rect r : callout)
        c.drawRect(r, paint);

      for (Rect r : glove)
        c.drawRect(r, paint);

      for (Rect r : clock)
        c.drawRect(r, paint);
*/
    }
  }

  // common padding
  private static int PADDING;
  private static int ELEVATION;

  // event listener
  private Listener listener;
  // areas
  private final Areas areas = new Areas();
  // views object (aggregates trick, talon and player views)
  private Views views;
  // currently moving card
  private final MovingCard mc = new MovingCard();
  // input layout
  private InputView inputView;
  // card selector
  private CardSelector cs = new CardSelector();
  // should we wait for touch
  boolean waitForTouch;

  private int countVisibleCards(List<CardView> list)
  {
    int visible = 0;

    for (CardView cv : list)
      if (cv.getVisibility() == VISIBLE)
        visible++;

    return visible;
  }

  private int countSuitGaps(Settings settings, List<CardView> list)
  {
    // only if suit spacing configured
    if (!settings.suitSpacing)
      return 0;

    int[] counts = {0, 0, 0, 0};

    // increment appropriate suit count only if the card is face up
    for (CardView cv : list)
      if (cv.getVisibility() == VISIBLE && cv.isFaceUp())
        ++counts[cv.card().suit.ordinal()];

    int result = 0;

    // summarize suits
    for (int n : counts)
      if (n > 0)
        ++result;

    // no cards face up - no gaps
    return result == 0 ? 0 : result - 1;
  }

  private void layoutTrick()
  {
    views.tv.measure(MeasureSpec.EXACTLY | areas.trick.width(), MeasureSpec.EXACTLY | areas.trick.height());
    views.tv.layout(areas.trick.left, areas.trick.top, areas.trick.right, areas.trick.bottom);
  }

  private void layoutHumanCards(Settings settings)
  {
    Rect area = areas.hand[0];
    List<CardView> list = views.hands.get(0);
    int n = countVisibleCards(list);
    int gaps = countSuitGaps(settings, list);
    int w = area.width();
    int spacing = w / 20;
    // next card offset
    double nco = ((double)w - CardView.WIDTH - spacing * gaps) / (n > Game.HAND_SIZE ? Game.HAND_SIZE + 1 : Game.HAND_SIZE - 1);
    // center the whole hand
    double left = area.left + (n > 0 ? (w - CardView.WIDTH - nco * (n - 1) - spacing * gaps) / 2 : 0);
    // previous visible card (to place spacing between suits)
    CardView previous = null;

    for (CardView cv : list)
      if (cv.getVisibility() != GONE)
      {
        // only if there is at least one gap
        if (gaps > 0 && previous != null && cv.card().suit != previous.card().suit)
          left += spacing;

        if (cs.contains(cv))
          cv.layout((int)left, area.top - ELEVATION);
        else
          cv.layout((int)left, area.top);

        left += nco;
        previous = cv;
      }
  }

  private void layoutAICards(Settings settings, int id)
  {
    List<CardView> list = views.hands.get(id);
    Rect area = areas.hand[id];
    int n = countVisibleCards(list);
    int h = area.height();
    int gaps = countSuitGaps(settings, list);
    int spacing = h / 30;
    // next card offset
    double nco = ((double)h - CardView.HEIGHT - spacing * gaps) / (n > Game.HAND_SIZE ? Game.HAND_SIZE + 1 : Game.HAND_SIZE - 1);
    // center the whole hand
    double top = area.top + (n > 0 ? (h - CardView.HEIGHT - nco * (n - 1) - spacing * gaps) / 2 : 0);
    // previous visible card (to place spacing between suits)
    CardView previous = null;

    for (CardView cv : list)
      if (cv.getVisibility() != GONE)
      {
        // only if there is at least one  gap
        if (gaps > 0 && previous != null && cv.card().suit != previous.card().suit)
          top += spacing;

        cv.layout(area.left, (int)top);

        if (cs.contains(cv))
        {
          if (id == 1)
            cv.layout(area.left + ELEVATION, (int)top);
          else
            cv.layout(area.left - ELEVATION, (int)top);
        }
        else
        cv.layout(area.left, (int)top);

        top += nco;
        previous = cv;
      }
  }

  private void layoutTalonCards()
  {
    Rect area = areas.talon;

    views.talon.get(0).layout(area.left, area.top);
    views.talon.get(1).layout(area.right - CardView.WIDTH, area.top);
  }

  private void layoutDropCards(Game g)
  {
    if (g.player.declarer != Game.NO_ID)
    {
      Rect area = areas.drop[g.player.declarer];
      CardView cv0 = views.drop.get(0);
      CardView cv1 = views.drop.get(1);

      cv0.measure(MeasureSpec.EXACTLY | CardView.WIDTH, MeasureSpec.EXACTLY | area.height());
      cv0.layout(area.left, area.top);

      cv1.measure(MeasureSpec.EXACTLY | CardView.WIDTH, MeasureSpec.EXACTLY | area.height());
      cv1.layout(area.right - CardView.WIDTH, area.top);
    }
  }

  private void showDropCards(Game g)
  {
    for (int i = 0; i < Game.TALON_SIZE; ++i)
      views.drop.get(i).set(Card.card(g.drop()[i]), true);
  }

  private void layoutMisereDesk(Game g, boolean landscape)
  {
    if (g.player.declarer != Game.NO_ID)
    {
      views.mdv.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

      int l = 0, w = views.mdv.getMeasuredWidth(), h = views.mdv.getMeasuredHeight();

      switch (g.player.declarer)
      {
        case 1:
          l = landscape ? GUI.center(w, PADDING, areas.callout[0].left) : GUI.center(w, PADDING, areas.trick.left);
          break;

        case 2:
          l = landscape ? GUI.center(w, areas.turn[0].right, areas.hand[2].right) : GUI.center(w, areas.trick.right, areas.hand[2].right);
          break;
      }

      int t = landscape ? areas.trickCounter[0].bottom - h : areas.trick.top + (areas.trick.height() - h) / 2;

      views.mdv.layout(l, t, l + w, t + h);
    }
  }

  private void layoutBid(int id)
  {
    Rect a = areas.callout[id];
    Callout tv = views.callouts[id];

    tv.measure(MeasureSpec.makeMeasureSpec(a.width(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(a.height(), MeasureSpec.EXACTLY));
    tv.layout(a.left, a.top, a.right, a.bottom);
  }

  private void layoutTrickCounter(int id)
  {
    views.tcv[id].layout(areas.trickCounter[id]);
  }

  private void layoutUndoDrop()
  {
    Rect area = areas.undoDrop;

    views.undoDrop.measure(MeasureSpec.EXACTLY | area.width(), MeasureSpec.EXACTLY | area.height());
    views.undoDrop.layout(area.left, area.top, area.right, area.bottom);
  }

  private void layoutOffer()
  {
    views.offer.measure(View.MeasureSpec.EXACTLY | areas.offer.width(), View.MeasureSpec.EXACTLY | areas.offer.height());
    views.offer.layout(areas.offer.left, areas.offer.top, areas.offer.right, areas.offer.bottom);
  }

  private void layoutInputControls(Settings settings)
  {
    if (settings.orientation == Settings.Orientation.PORTRAIT)
    {
      int t0 = Math.max(areas.trickCounter[1].bottom, areas.talon.bottom),
          l = areas.trickCounter[1].left,
          w = areas.trickCounter[2].right - l,
          h0 = areas.trickCounter[0].top - t0,
          t1 = areas.trickCounter[1].bottom,
          h1 = areas.trickCounter[0].top - t1;

      //inputView.layout.measure(MeasureSpec.EXACTLY | w, MeasureSpec.EXACTLY | (areas.trickCounter[0].top - areas.trickCounter[1].bottom));

      inputView.frame0.measure(MeasureSpec.EXACTLY | w, MeasureSpec.EXACTLY | h0);
      inputView.frame0.layout(l, t0, l + w, t0 + h0);

      inputView.frame1.measure(MeasureSpec.EXACTLY | w, MeasureSpec.EXACTLY | h1);
      inputView.frame1.layout(l, t1, l + w, t1 + h1);

      //inputView.layout.layout(l, t, l + w, t + inputView.layout.getMeasuredHeight());

      inputView.frame2.measure(MeasureSpec.EXACTLY | areas.bo.width(), MeasureSpec.EXACTLY | areas.bo.height());
      //inputView.frame2.layout(areas.bo.left, areas.bo.top, areas.bo.right, areas.bo.bottom);
      inputView.frame2.layout(areas.bo.left, areas.bo.top, areas.bo.right, areas.bo.bottom);
    }
    else
    {
      int t0 = Math.max(areas.trickCounter[1].bottom, areas.talon.bottom),
          t1 = areas.trickCounter[1].top,
          l = areas.trickCounter[1].right,
          r = areas.trickCounter[2].left,
          w = r - l,
          b = areas.trickCounter[0].top,
          h0 = b - t0,
          h1 = b - t1;

      inputView.frame0.measure(MeasureSpec.EXACTLY | w, MeasureSpec.EXACTLY | h0);
      inputView.frame0.layout(l, t0, r, b);
      inputView.frame1.measure(MeasureSpec.EXACTLY | w, MeasureSpec.EXACTLY | h1);
      inputView.frame1.layout(l, t1, r, b);
    }
  }
}
